<template>
  <div id="app">
    <h1 v-color="color">APP根组件 --- {{ countFromSon }}</h1>
    <p class="goto-about" @click="$router.push('/about')">去往about</p>
    <p class="goto-about" @click="$router.push('/home')">去往home</p>
    <!-- 原始形式应用 -->
    <h2>原始形式获取state状态 --- {{ $store.state.vuexCount }}</h2>
    <!-- 计算属性应用 -->
    <h2>计算属性形式获取state状态 --- {{ computedCount }}</h2>
    <h2>mapState形式获取state状态 --- {{ vuexCount }}</h2>
    <h2>mapState形式获取state状态 --- {{ vuexObj }}</h2>
    <h2>原始形式获取getters: {{ $store.getters.filterList }}</h2>
    <h2>mapGetters获取getters: {{ filterList }}</h2>
    <!-- 用户的token和应用名称name -->
    <!-- $store.state.子模块.属性 -->
    <h3>原始形式获取形式子模块token: {{ $store.state.user.token }} --- 子模块setting: {{ $store.state.setting.name }} </h3>
    <h3>mapGetters形式获取子模块token: {{ token }} --- 子模块setting: {{ name }} </h3>
    <h2>展开运算符与计算属性: {{ zid }}</h2>
    <hr>
    <h3>book : {{ book }}</h3>
    <h3>foo : {{ foo }}</h3>
    <h3>bar : {{ bar }}</h3>
    <button @click="color = 'green'">改变根组件标题的颜色</button>
    <hr>
    <Article>
      <template #title>
        <h2 v-color="'#eae3d1'">春日偶成</h2>
        <h5 style="margin: 0 auto;">作者：程颢</h5>

      </template>
      <!-- 组件使用者 在使用作用域插槽时, 一般给属性设置形参名称为scope, 提示这是一个作用域插槽 -->
      <!-- 注意 scope 肯定是一个对象!!!, 因为把标签中的属性传过来, 属性都是有键值对存在的  -->
      <!-- 单个属性也是也值的, 如复选框的 disabled属性, 它的值就是 "" -->
      <!-- scope 是一个对象, 对象中每个属性值可以是其他数据类型 -->
      <template #content="scope">
        <h3>云淡风轻近午天，傍花随柳过前川。</h3>
        <h3>时人不识余心乐，将谓偷闲学少年。</h3>
        <p> {{ scope }}</p>   <!-- { "user": { "name": "llhuang10", "sex": "man" }, "msg": "hello vue.js" } -->
        <p> {{ scope.msg }}</p>         <!-- hello vue.js -->
        <p> {{ scope.user }}</p>        <!-- { "user": { "name": "llhuang10", "sex": "man" } -->
        <p> {{ scope.user.name }}</p>   <!-- llhuang10 -->
        <p> {{ scope.user.sex }}</p>    <!-- man -->
      </template>

      <template #footer="scope">
        <p>© 2021 古诗文网 | 诗文 | 名句 | 作者 | 古籍 | 纠错</p>
        <p>{{ scope }}</p>              <!-- { "ids": [ 1, 2, 3, 4, 5 ], "baidu": "" } -->
      </template>
    </Article>
    <!--<div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>-->
    <button @click="retryLeftCount">重置 Left 组件的 count为 0</button>
    <button @click="flag = !flag">Toggle Flag</button>
    <hr>
    <input v-if="inputVisible" ref="iptRef" type="text" @blur="showBtn">
    <button v-else @click="showInput">展示输入框</button>
    <hr>
    <STest v-show="flag" info="9"></STest>
    <div class="box">
      <!-- 渲染 Left组件和Right组件 -->
      <!-- 步骤3: 以 标签 的形式使用刚才注册的组件 -->
      <div class="left">
        <Left ref="leftRef"></Left>

        <!--<left></left>-->
      </div>

      <!-- Vue.component('MyCount', Count) -->
      <!-- 这两种书写方式都可以 -->
      <div style="display: none">
        <my-count :init="21" :obj="per"></my-count>
        <MyCount :init="12" :obj="{hello: 'world'}"></MyCount>
      </div>
      <div>
        <!-- 这种方式实现了 Tab栏 效果!!! -->
        <button @click="comName = 'MyCount'">展示 MyCount</button>
        <button @click="comName = ''">隐藏 MyCount</button>

        <!-- 1.component标签是 vue 内置的, 作用: 组件的占位符 -->
        <!-- 2.is 属性的值, 表示要渲染的组件的名字 -->
        <!-- keep-alive 可以把内部的组件进行缓存, 而不是进行销毁组件 -->
        <!--  keep-alive 的属性 包含include 和 排除exclude, 它们不能同时用 -->
        <!--  include: 只有名称匹配的组件才会被缓存, 多个组件名用 , 分开 -->
        <!--  exclude: 任何具有匹配名称的组件都不会被缓存, 多个组件名用 , 分开 -->

        <!-- 注意: 匹配首先检测组件自身的name选项, 如export default {name: 'Count', xx) -->
        <!--       如果 name 选项不可用!!!(要理解什么是不可用), 才会匹配它的局部注册名称 -->
        <!--       局部注册名称(父组件 component选项的键值), 匿名组件不能被匹配 (匿名组件是什么?)-->
        <!-- 这也解决下面三种写法: include="foobar" include="MyCount" include="my-count" -->
        <!-- 没有匹配到Count组件的原因, 首先使用组件的自身 name 名称, 也就是 Count, 当name选项不可用, 才会用注册名称 -->
        <keep-alive include="Count">
          <component :is="comName" :init="-99"></component>
        </keep-alive>
      </div>

      <div class="right">
        <Right @numChange="getNewCount"></Right>
        <!--<right></right>-->
      </div>

    </div>
    <h2>使用 sync修饰符 countSync = {{ countSync }}</h2>
    <Bottom :countProp.sync="countSync">
      <!-- 默认情况下, 在使用组件的时候, 提供的内容都会填充到name为default的插槽之中 -->
      <!-- 1.如果要把内容填充到指定名称的插槽中, 需要使用v-slot: 这个指令-->
      <!-- 2.v-slot: 后面跟上插槽的名字, 不需要带引号-->
      <!-- 3.v-slot: 指令不能直接用在元素身上, 必须用到 template 标签上 -->
      <!-- 4.template 这个标签, 它是衣蛾虚拟的标签, 只起到包裹性质的作用. -->
      <!--   但是, 不会被渲染为任何实质性的 html 元素-->
      <!-- 5.v-slot: 指令的简写形式是 # -->
      <!--<template v-slot:default>-->
      <template #default>

        <!-- <p v-slot:default>xxx</p> 这样使用 v-slot 指令, 会出现下面的错误 -->
        <!-- 说明 v-slot 指令只能用 components or <template> -->
        <!-- Errors compiling template: v-slot can only be used on components or <template>.-->
        <p>这是在 Bottom 组件的内容区域, 声明的p标签</p>
      </template>
    </Bottom>
    <hr>
    <!-- 使用BetterScroll中的 slide滑动插件, 制作的轮播图效果 -->
    <Carousel></Carousel>
  </div>
</template>

<script>
/**
 * 步骤1: 使用import语法导入需要的组件
 */
// @ ==> webpack.config.js中配置 path.join(__dirname, './src/') 使用绝对路径的方式
import Article from '@/components/Article'
import Left from '@/components/Left'
import Right from '../components/Right'
import Bottom from '@/components/Bottom'
import STest from '@/components/STest'
import Carousel from '@/components/Carousel'
import { mapState, mapGetters } from 'vuex'
import { book, getNewObj } from '@/assets/constant'

export default {
  components: {
    /**
     * 步骤2: 使用components节点注册组件
     * 全写模式
     * 'Right': Right  |  Right: Right
     * 简写模式   ES6语法
     * Right
     */
    Left,
    Right: Right,
    STest,
    Bottom,
    Article,
    Carousel
  },

  computed: {
    computedCount () {
      // 把 state 中数据, 定义在组件内的计算属性中
      return this.$store.state.vuexCount
    },
    book,
    ...getNewObj(),
    // ...this.book,
    // 3.mapState是辅助函数, 帮助我们把store中的数据映射到组件的计算属性中, 它属于一种方便用法
    // mapState返回是一个对象, 所以可以利用扩展运算符将导出的状态映射给计算属性
    // 辅助函数返回是的一个对象, 且对象的属性值是一个函数!!!
    ...mapState(['vuexCount', 'vuexObj']),
    // 使用对象展开运算符, 将getters中的计算属性导入到组件的计算属性中
    ...mapGetters(['filterList', 'name', 'token'])
  },
  /**
   * 私有自定义指令的节点
   */
  directives: {
    /**
     * 定义名为 color 的指令, 指向一个配置对象
     * 注意: 当指令的 bind 和 update 函数中的逻辑完全一样, 可以简写, 简写看PPT, 第5天
     */
    color: {
      /**
       * 当指令第一次被绑定到元素上的时候, 会立即触发 bind 函数
       * 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
       * v-color="color" -> color 值传递给 binding 参数
       * #
       * 注意: bind 函数只会调用一次: 当指令第一次绑定到元素时调用
       * @param el
       * @param binding
       */
      bind (el, binding) {
        // <h1 data-v-7ba5bd90="">APP根组件 --- 0</h1>
        // console.log(el)

        /*  v-color="color" 对应的 binding 对象信息
        {
            "name": "color",
            "rawName": "v-color",
            "value": "skyblue",
            "expression": "color",
            "modifiers": {},
            "def": {}
        }
           v-color="'#eae3d1'" 对应的 binding 对象信息
        {
            "name": "color",
            "rawName": "v-color",
            "value": "#eae3d1",
            "expression": "'#eae3d1'",
            "modifiers": {},
            "def": {}
        }
         */
        /**
         * 自定义指令使用方式 v-color="color" 和  v-color="'#eae3d1'" 对比
         * 通过对比两个binding对象的信息中 value 才是能用的, expression是入参值 -> 表达式
         */
        // console.log(binding)
        el.style.color = binding.value
        console.log('触发了 v-color 的 bind 函数')
      },
      /**
       * 在每次 DOM 更新的时候, 会触发 update 函数, 而 bind 函数不会触发. 也就说 update 会触发多次
       * 注意: 在指令第一次绑定元素的时候是不会触发 update 函数, 只会触发 bind 函数
       */
      update (el, binding) {
        el.style.color = binding.value
        console.log('触发了 v-color 的 update 函数')
      }
    }
  },
  data () {
    return {
      flag: true,
      // 定义一个
      countFromSon: 0,
      countSync: 1000,
      // 控制输入框和按钮的按需求换, 默认false -> 展示按钮, 隐藏输入框
      inputVisible: false,
      // comName 表示要展示的组件的名字
      comName: 'MyCount',
      color: 'skyblue',
      persons: [
        {
          id: 12,
          name: 'hliushi',
          address: 'Anhui'
        },
        {
          id: 13,
          name: 'springandme',
          address: 'shanghai'
        },
        {
          id: 14,
          name: 'llhuang10',
          address: 'HuaiNan'
        },
        {
          id: 19,
          name: 'zid',
          address: 'LiuAn'
        }
      ],
      per: {
        name: 'llhuang10',
        pic: 'xxxx'
      },
      func: mapState
    }
  },
  methods: {
    /**
     * 获取子组件传递过来的数据
     * @param val
     */
    getNewCount (val) {
      console.log('numChange 事件被触发了!', val)
      this.countFromSon = val
    },
    /**
     * 点击按钮, 重置 Left组件 中 Count组件 中的count值
     */
    retryLeftCount () {
      // this.$refs.leftRef 获取到App.vue组件的子组件Left中引用
      // this.$refs.leftRef.$refs.countRef 获取到Left组件中子组件中countRef组件
      // 获取了对应组件的引用, 可以使用它们的所有属性和方法
      console.log(this.$refs.leftRef.$refs.countRef)
      this.$refs.leftRef.$refs.countRef.retryCount()
    },
    /**
     * 点击按钮, 展示输入框
     */
    showInput () {
      // 1.切换布尔值, 把文本框展示出来
      this.inputVisible = true
      console.log(this.$refs.iptRef)  // undefined
      // 2.让展示出来的文本框, 自动获取输入框的焦点
      // 但是实现不了, 我的解决方式在updated生命周期函数获取焦点

      /**
       * TypeError: Cannot read property 'focus' of undefined
       * 如果理解这类错误信息, 找到focus的调用者 -> iptRef, iptRef为undefined
       * #
       * 出现原因: 使用的v-if="inputVisible", 控制按钮和输入框切换都会重新渲染DOM结构
       * 重新渲染到页面上都会触发生命周期函数 beforeUpdate 和 update 方法
       * 所以当切换布尔值的值, 此时DOM中input元素, 还没有重新被渲染到页面上, 所以它的引用肯定是undefined
       */
      // this.$refs.iptRef.focus()
      /**
       * 解决方式1: 使用 this.$nextTick(cb)方法, $nextTick(cb)是Vue构造函数的原型对象方法
       * 组件的$nextTick(cb)方法, 会把cb回调函数推迟到下一个DOM更新周期之后执行
       * 通俗的理解是: 等组件的DOM更新完成之后, 再执行cb回调函数.
       * 从而能保证cb回调函数可以操作最新的DOM元素
       */
      this.$nextTick(() => {
        console.log('------ this.$nextTick ------ ')
        this.$refs.iptRef.focus()
      })
    },
    /**
     * 输入框失去焦点, 展示按钮
     */
    showBtn () {
      this.inputVisible = false
    }
  },
  updated () {
    if (this.$refs.iptRef !== undefined) {
      // this.$refs.iptRef.focus()
    }
  },

}
</script>


<style lang="less" scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;

  button {
    height: 25px;
    vertical-align: bottom;
  }

  input {
    height: 25px;
    box-sizing: border-box;
    vertical-align: bottom;
  }

  .box {
    display: flex;
    justify-content: space-around;
    margin-top: 15px;

    .left {
      width: 250px;
      height: 250px;
      background-color: skyblue;
      text-align: center;
    }

    .right {
      width: 250px;
      height: 250px;
      background-color: coral;
      text-align: center;
    }

    h2 {
      margin: 0;
      padding: 0;
      line-height: 250px;
    }
  }

}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}

.goto-about {

  cursor: pointer;

  &:hover {
    color: #42b983;
  }
}
</style>
